import re
import bpy
import datetime
from typing import Union, List, Tuple
from os.path import join, splitext
from ...addon.naming import FluidLabNaming
from ...addon.paths import FluidLabPreferences
from bpy.types import Context, Operator, Scene, Object
from ...libs.functions.get_common_vars import get_common_vars
from ...properties.lists.props_list_fluid_mesh import FluidMeshListItem
from ...libs.functions.collections import set_active_collection_to_master_coll, create_new_collection, set_active_collection_by_name


EXT_ABC = ".abc"
EXT_USD = ".usd"
EXT_OBJ = ".obj"


class CommmonClass(Operator):
    bl_idname = "fluidlab.common_exports"
    bl_label = "Common Exports"
    bl_description = "Common Exports code"
    bl_options = {"REGISTER", "UNDO"}


    def prepare_collections(self, context:Context, ext:str) -> None:
        
        # Si no existe se crea y se deja activa FluidLab:
        set_active_collection_to_master_coll(context)

        target_coll_name = FluidLabNaming.EXPORTS_ABC if ext == ".abc" else FluidLabNaming.EXPORTS_USD

        # Group collection:
        new_coll = bpy.data.collections.get(target_coll_name)
        if not new_coll:
            new_coll = create_new_collection(context, target_coll_name, False)

        set_active_collection_by_name(context, target_coll_name)


    def get_all_org_obs_from_mesh_list(self, fluid_mesh) -> Tuple[List[FluidMeshListItem], List[Object]]:
        all_mesh_items = fluid_mesh.get_all_items
        all_exportable_items = [item for item in all_mesh_items if item.exportable]
        all_obs = [item.ob for item in all_exportable_items]
        return all_exportable_items, all_obs


    def prepare_name(self, ext:str) -> Union[str, None]:
        final_name = None
        now = datetime.datetime.now()
        date_hash = "_moth_" + str(now.month) + "_day_" + str(now.day) + "_" + str(now.hour) + "h_" + str(now.minute) + "m_" + str(now.second) + "s" + ext
        
        if bpy.data.is_saved:
            blend_file_name = bpy.path.basename(bpy.context.blend_data.filepath)
            name = splitext(blend_file_name)[0] 
            final_name = name + date_hash
        else:
            final_name = "unsaved_scene" + date_hash
        
        return final_name


    def alembic_export(self, fpath:str, scn:Scene) -> None:
        # chuleta https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.alembic_export
        bpy.ops.wm.alembic_export(
            filepath=fpath, 
            check_existing=True, 
            start=scn.frame_start, 
            end=scn.frame_end, 
            xsamples=1, 
            gsamples=1,  
            selected=True, 
            visible_objects_only=True, 
            flatten=False,  # Aplanar la jerarquía (opcional)
            uvs=True, 
            packuv=True, 
            normals=True, 
            vcolors=False, 
            orcos=True,  # Exportar información de color (opcional)
            face_sets=False, 
            subdiv_schema=False, 
            apply_subdiv=True, 
            curves_as_mesh=True, 
            use_instancing=True, 
            global_scale=1.0, 
            triangulate=False,
            quad_method='SHORTEST_DIAGONAL', 
            ngon_method='BEAUTY', 
            export_custom_properties=True, 
            as_background_job=False,  # Ejecutar como trabajo en segundo plano (opcional) (Para ser sincrono tiene q ser False)
            evaluation_mode='VIEWPORT', 
            init_scene_frame_range=True 
        )
    

    def alembic_import(self, fpath:str) -> None:
        # chuleta import https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.alembic_import
        bpy.ops.wm.alembic_import(
            filepath=fpath, 
            relative_path=True, 
            filter_glob='*.abc', 
            scale=1.0, 
            set_frame_range=True, 
            validate_meshes=False, 
            always_add_cache_reader=True, 
            is_sequence=False, 
            as_background_job=False
        )


    def usd_export(self, fpath:str) -> None:
        # chuleta https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.usd_export
        bpy.ops.wm.usd_export(
            filepath=fpath, 
            check_existing=True, 
            filter_glob='*.usd', 
            selected_objects_only=True, 
            visible_objects_only=True, 
            export_animation=True, 
            export_hair=False, 
            export_uvmaps=True, 
            export_mesh_colors=True, 
            export_normals=True, 
            export_materials=True, 
            use_instancing=False, 
            evaluation_mode='VIEWPORT', 
            generate_preview_surface=True, 
            export_textures=True, 
            overwrite_textures=False, 
            relative_paths=True, 
        )


    def usd_import(self, fpath:str) -> None:
        # chuleta https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.usd_import
        bpy.ops.wm.usd_import(
            filepath=fpath, 
            check_existing=True, 
            relative_path=True, 
            filter_glob='*.usd', 
            scale=1.0, 
            set_frame_range=True, 
            # import_cameras=True, 
            # import_curves=True, 
            # import_lights=True, 
            import_materials=True, 
            import_meshes=True, 
            # import_volumes=True, 
            import_shapes=True, 
            # import_skeletons=True, 
            # import_blendshapes=True, 
            import_subdiv=True, 
            import_instance_proxies=True, 
            import_visible_only=True, 
            create_collection=False, 
            read_mesh_uvs=True, 
            read_mesh_colors=True, 
            read_mesh_attributes=True, 
            import_guide=False, 
            import_proxy=True, 
            # import_render=True, 
            import_all_materials=False, 
            import_usd_preview=True, 
            set_material_blend=True, 
            light_intensity_scale=1.0, 
            mtl_name_collision_mode='MAKE_UNIQUE', 
            import_textures_mode='IMPORT_PACK', 
            import_textures_dir='//textures/', 
            tex_name_collision_mode='USE_EXISTING'
        )
    

    def obj_export(self, fpath:str, scn:Scene) -> None:
        # chuleta https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.obj_export
        bpy.ops.wm.obj_export(
            filepath=fpath, 
            check_existing=False, 
            export_animation=False, 
            start_frame=scn.frame_current, 
            end_frame=scn.frame_current, 
            forward_axis='NEGATIVE_Z', 
            up_axis='Y', 
            global_scale=1.0, 
            apply_modifiers=True, 
            export_eval_mode='DAG_EVAL_VIEWPORT', 
            export_selected_objects=True, 
            export_uv=True, 
            export_normals=True, 
            export_colors=True, 
            export_materials=True, 
            export_pbr_extensions=True, 
            path_mode='AUTO', 
            export_triangulated_mesh=False, 
            export_curves_as_nurbs=False, 
            export_object_groups=True, 
            export_material_groups=True, 
            export_vertex_groups=True, 
            export_smooth_groups=True, 
            smooth_group_bitflags=False, 
            filter_glob='*.obj;*.mtl'
        )
    

    def obj_import(self, fpath:str) -> None:
        # chuleta import https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.obj_import
        bpy.ops.wm.obj_import(
            filepath=fpath, 
            # directory='', 
            # files=None, 
            # check_existing=False, 
            display_type='DEFAULT', 
            sort_method='DEFAULT', 
            global_scale=1.0, 
            clamp_size=0.0, 
            forward_axis='NEGATIVE_Z', 
            up_axis='Y', 
            use_split_objects=True, 
            use_split_groups=False, 
            import_vertex_groups=True, 
            validate_meshes=False, 
            filter_glob='*.obj;*.mtl'
        )


    def hide_original_mesh(self, all_exportable_items:List[FluidMeshListItem], org_obs:List[Object]) -> None:
        [setattr(item, "show_viewport", False) for item in all_exportable_items] # desactivo los modifiers de los remesh
        [ob.hide_set(True) for ob in org_obs] # oculto con ojito
        [setattr(ob, "hide_render", True) for ob in org_obs] # oculto de render


    def renaming_and_transfer_material(self, context:Context, org_obs:List, ext:str) -> None:
        new_obs = context.selected_objects
        for new_ob, org_ob in zip(new_obs, org_obs):

            # los renombro a su nombre.abc:
            result = re.search(r"^(.*)(\.)", new_ob.name)
            if result:
                new_ob.name = result.group(1) + ext
            else:
                if ext not in new_ob.name:
                    new_ob.name += ext

            # le reasigno los materiales:
            if org_ob.data.materials:
                new_ob.data.materials.clear()
                for mat in org_ob.data.materials:
                    new_mat = mat.copy()
                    new_ob.data.materials.append(new_mat)


    def main_execute(self, context:Context, ext:str) -> Union[set, None]:

        # Comprobando si se a definido el path para los exports:
        addon_preferences = FluidLabPreferences.get_prefs(context)
        fpath = addon_preferences.exports_path
        if not fpath:
            self.report({'ERROR'}, "Not valid exports path!")
            return {'CANCELLED'}
        
        scn, fluid_mesh, ui = get_common_vars(context, get_scn=True, get_fluid_mesh=True, get_ui=True)
        scn.frame_set(scn.frame_start)
        bpy.ops.object.select_all(action='DESELECT')
        
        all_exportable_items, all_obs = self.get_all_org_obs_from_mesh_list(fluid_mesh)
        
        # Desoculto los objetos para poder seleccionarlos (podrían estar ocultos los originales si ya se exporto previamente):
        [(ob.hide_set(False), ob.select_set(True)) for ob in all_obs]
        
        # Preparando el path name:
        final_name = self.prepare_name(ext)

        # Preparando el path del archivo.ext:
        fpath = join(fpath, final_name)
        
        # Si estoy usando alembic exporto en alembic, sino en usd:
        self.alembic_export(fpath, scn) if ext == ".abc" else self.usd_export(fpath)
        
        if ui.auto_import:

            self.prepare_collections(context, ext)

            # Si estoy usando alembic importo en alembic, sino en usd:
            self.alembic_import(fpath) if ext == ".abc" else self.usd_import(fpath)

            if ui.hide_original_mesh:
                self.hide_original_mesh(all_exportable_items, all_obs)
                        
            self.renaming_and_transfer_material(context, all_obs, ext)


class FLUIDLAB_OT_export_abc(CommmonClass):
    bl_idname = "fluidlab.export_abc"
    bl_label = "Export Alembic"
    bl_description = "Export selected meshes to Alembic"
    bl_options = {"REGISTER", "UNDO"}

    
    def execute(self, context):
        self.main_execute(context, EXT_ABC)
        return {'FINISHED'}


class FLUIDLAB_OT_export_usd(CommmonClass):
    bl_idname = "fluidlab.export_usd"
    bl_label = "Export USD"
    bl_description = "Export selected meshes to USD"
    bl_options = {"REGISTER", "UNDO"}

    
    def execute(self, context):
        self.main_execute(context, EXT_USD)
        return {'FINISHED'}


class FLUIDLAB_OT_export_obj(CommmonClass):
    bl_idname = "fluidlab.export_obj"
    bl_label = "Export OBJ"
    bl_description = "Export selected meshes to OBJ"
    bl_options = {"REGISTER", "UNDO"}

    
    def execute(self, context):

        # Comprobando si se a definido el path para los exports:
        addon_preferences = FluidLabPreferences.get_prefs(context)
        fpath = addon_preferences.exports_path
        if not fpath:
            self.report({'ERROR'}, "Not valid exports path!")
            return {'CANCELLED'}

        active_ob = context.active_object
        if not active_ob or active_ob.type != 'MESH':
            self.report({'ERROR'}, "Invalid active object!")
            return {'CANCELLED'}
        
        scn, ui = get_common_vars(context, get_scn=True, get_ui=True)

        # Deselecciona todos los objetos
        bpy.ops.object.select_all(action='DESELECT')
        # Selecciona el objeto
        active_ob.select_set(True)

        # Preparando el path name:
        final_name = self.prepare_name(EXT_OBJ)

        # Preparando el path del archivo.abc:
        fpath = join(fpath, final_name)

        self.obj_export(fpath, scn)

        if ui.auto_import:
            self.obj_import(fpath)

            # Ocultamos los mesh originales:
            if ui.hide_original_mesh:
                active_ob.hide_set(True) # oculto con ojito
                setattr(active_ob, "hide_render", True) # oculto de render

            new_obs = context.selected_objects
            for new_ob in new_obs:

                # los renombro a su nombre.abc:
                result = re.search(r"^(.*)(\.)", new_ob.name)
                if result:
                    new_ob.name = result.group(1) + EXT_OBJ
                else:
                    if EXT_OBJ not in new_ob.name:
                        new_ob.name += EXT_OBJ

                # le reasigno los materiales:
                if active_ob.data.materials:
                    new_ob.data.materials.clear()
                    for mat in active_ob.data.materials:
                        new_mat = mat.copy()
                        new_ob.data.materials.append(new_mat)


        print(f"Object exported at frame {scn.frame_current} as {fpath}")

        return {'FINISHED'}
